/**
 * Author(s): xuming.Wong <xuming.Wong@gmail.com>
 */

#ifndef __PQ_MathUtil_H__
#define __PQ_MathUtil_H__

#include "EnginePrerequisites.h"

#include <float.h>

#include "OgreVector3.h"
#include "OgreVector2.h"
#include "OgreMatrix3.h"
#include "OgreMatrix4.h"
#include "OgreMath.h"
#include "OgreQuaternion.h"
#include "OgreRectangle.h"

#include "LinearMath/btVector3.h"
#include "LinearMath/btQuaternion.h"


// Rotation in degrees true, else false
#define PQ3D_ROTATION_UNIT true

#if OGRE_DOUBLE_PRECISION == 1
# define EPSILON   DBL_EPSILON
# define INFINITY  DBL_MAX
#else
# define PQ3D_EPSILON   FLT_EPSILON
# define PQ3D_INFINITY  FLT_MAX
#endif


#define PQ3D_PI  Ogre::Real(3.141592653589793238)
#define PQ3D_PI2 Ogre::Real(6.283185307179586476)//pi squared
#define PQ3D_Pih Ogre::Real(1.570796326794896619)
#define PQ3D_RPD Ogre::Real(0.017453292519943295)//rad per degree
#define PQ3D_DPR Ogre::Real(57.295779513082320876)//degree per rad

#define PQ3D_Max( lhs, rhs )		((lhs) < (rhs) ? (rhs) : (lhs))
#define PQ3D_Min( lhs, rhs )		((rhs) < (lhs) ? (rhs) : (lhs))
#define PQ3D_Clamp( x, mi, ma )	((x) <  (mi) ?  (mi)  : (x) > (ma) ? (ma) : (x))
#define PQ3D_Abs( x )				((x)  < ( 0 ) ? -(x) : (x))
#define PQ3D_Fuzzy( x )			(PQ3D_Abs( x ) < PQ3D_EPSILON)
#define PQ3D_FuzzyT( x, t)			(PQ3D_Abs( x ) < t)
#define PQ3D_Degrees( x )			(Ogre::Real(x) * DPR)
#define PQ3D_Radians( x )			(Ogre::Real(x) * RPD)
#define PQ3D_IsInf( x )			(PQ3D_Abs( x ) >= PQ3D_INFINITY)
#define PQ3D_FuzzyEq(x, y)			(PQ3D_Fuzzy((x)- (y)))
#define PQ3D_FuzzyEqT(x, y, t)		(PQ3D_FuzzyT( (x)- (y), (t)))
#define PQ3D_FuzzyVec(v)			(PQ3D_Fuzzy(v.squaredLength()))

namespace PQEngine
{
	class MathUtil
	{
	public:
		static float randFloat(float min, float max);

		inline static float randFloat(float v)
		{
			return randFloat(-v,v);
		}

		static bool powerOfTwo(int num);

		inline static btVector3 toBullet(const Ogre::Vector3 &V)
		{
			return btVector3(V.x, V.y, V.z);
		}

		inline static btQuaternion toBullet(const Ogre::Quaternion &Q)
		{
			return btQuaternion(Q.x, Q.y, Q.z, Q.w);
		}

		inline static Ogre::Vector3 toOgre(const btVector3 &V)
		{
			return Ogre::Vector3(V.x(), V.y(), V.z());
		}

		inline static Ogre::Quaternion toOgre(const btQuaternion &Q)
		{
			return Ogre::Quaternion(Q.w(), Q.x(), Q.y(), Q.z());
		}

		static Ogre::Matrix3 getMat3FromEuler(const Ogre::Vector3& eul, bool isDegree= PQ3D_ROTATION_UNIT);
		static Ogre::Quaternion getQuatFromEuler(const Ogre::Vector3 &eul, bool isDegree= PQ3D_ROTATION_UNIT);
		static Ogre::Vector3 getEulerFromQuat(const Ogre::Quaternion& q, bool toDegree= PQ3D_ROTATION_UNIT);

		/// for blender matrices (transpose)
		static Ogre::Matrix4 getFromFloat(const float m[][4]);
		static Ogre::Matrix4 getFromFloat3(const float m[][3]);


		static void extractLocEulScale(const Ogre::Matrix4 &m, Ogre::Vector3 &loc, Ogre::Vector3 &eul, Ogre::Vector3 &scale, bool inDegrees= PQ3D_ROTATION_UNIT);
		static void makeLocEulScale(Ogre::Matrix4 &dest, const Ogre::Vector3& loc, const Ogre::Vector3& eul, const Ogre::Vector3 &scale, bool inDegrees= PQ3D_ROTATION_UNIT);
		static void makeLocQuatScale(Ogre::Matrix4 &dest, const Ogre::Vector3& loc, const Ogre::Quaternion& q, const Ogre::Vector3 &scale);

		static Ogre::Quaternion interp(const Ogre::Quaternion &a, const Ogre::Quaternion &b, Ogre::Real t, bool fast=true);
		static Ogre::Vector2 interp(const Ogre::Vector2 &a, const Ogre::Vector2 &b, Ogre::Real t);
		static Ogre::Vector3 interp(const Ogre::Vector3 &a, const Ogre::Vector3 &b, Ogre::Real t);
		static Ogre::Real interp(const Ogre::Real &a, const Ogre::Real &b, Ogre::Real t);


		static Ogre::Quaternion getDiffQuat(const Ogre::Quaternion &from, const Ogre::Quaternion &to);

		static void extractTransform2(const Ogre::Matrix4& m, Ogre::Vector3 &loc, Ogre::Quaternion &quat, Ogre::Vector3 &scale);
		static void extractTransform(const Ogre::Matrix4& m, Ogre::Vector3 &loc, Ogre::Quaternion &quat, Ogre::Vector3 &scale);
		static void getBlendMatrix(Ogre::Matrix4& dest, const Ogre::Matrix4 &A, const Ogre::Matrix4 &B, Ogre::Real fact);
		static void extractMatrix(const float m[][4], Ogre::Vector3 &loc, Ogre::Quaternion &quat, Ogre::Vector3 &scale);

		// adapted from bullet
		static Ogre::Vector3 calculateLinearVelocity(const Ogre::Vector3& from, const Ogre::Vector3& to, Ogre::Real timeStep);
		static Ogre::Vector3 calculateAngularVelocity(const Ogre::Quaternion& from, const Ogre::Quaternion& to, Ogre::Real timeStep);
		static Ogre::Quaternion calculateRotation(const Ogre::Quaternion& cur, const Ogre::Vector3& angular, Ogre::Real timeStep);
	};
}
#endif
